/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2020-2021.
 * Description: L3 cache file
 * Author: yanbo <joey.yanbo@huawei.com>
 * Create: 2020-11-19
 */
#include <linux/init.h>
#include <linux/spinlock.h>
#include <linux/io.h>
#include <linux/interrupt.h>
#include <linux/module.h>
#include <asm/cacheflush.h>
#include <linux/io.h>
#include <asm/mach/map.h> /* ioremap */
#include <linux/proc_fs.h>
#include <linux/uaccess.h>
#include <linux/init.h>
#include <linux/slab.h>
#include "mach/l3_cache.h"
#ifdef CONFIG_OF
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/of_fdt.h>
#include <linux/of_irq.h>
#include <linux/of_platform.h>
#endif
#include <mach/platform.h>
#include <linux/hal/cpu_param.h>

static DEFINE_SPINLOCK(l3cache_lock);

/* L3 Cache register base virtual address */
unsigned int l3_base_addr;
EXPORT_SYMBOL(l3_base_addr);
#define CACHE_L3_MAINT_CTRL_REG	HI1381_CACHE_L3_MAINT_CTRL_REG
#ifdef	CONFIG_OF
static const struct of_device_id l3cache_ids[] __initconst = {
	{.compatible = "arm,cortex-a15-l3cache", .data = NULL},
	{}
};
#endif

static inline void cache_sync(void)
{
}

#ifdef CONFIG_OUTER_CACHE_SYNC
static void l3cache_cache_sync(void)
{
	return;
}
#endif

#define l3cache_writel(a, b)	writel((a), (volatile void __iomem *)(b))
#define l3cache_readl(a)	readl((volatile void __iomem *)(a))

static inline void l3cache_inval_all_nolock(void)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	u32 tmp;

	/* step 1:invalidate all */
	l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
	l3_maint_ctl.bits.mnt_range = L3_MAINT_FOR_ALL;
	l3_maint_ctl.bits.mnt_type = L3_MAINT_INVAL;
	l3_maint_ctl.bits.mnt_start = L3_MAINT_START;
	l3cache_writel(l3_maint_ctl.value, l3_base_addr + CACHE_L3_MAINT_CTRL_REG);

	/* step 2:waiting Maintain end */
	do {
		l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
		tmp = l3_maint_ctl.bits.mnt_start;
	} while (tmp != L3_MAINT_END);
	return;
}

static void l3cache_flush_all_nolock(void)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	u32 tmp;

	/* step 1:flush all */
	l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
	l3_maint_ctl.bits.mnt_range = L3_MAINT_FOR_ALL;
	l3_maint_ctl.bits.mnt_type = L3_MAINT_FLUSH;
	l3_maint_ctl.bits.mnt_start = L3_MAINT_START;
	l3cache_writel(l3_maint_ctl.value, l3_base_addr + CACHE_L3_MAINT_CTRL_REG);

	/* step 2:waiting Maintain end */
	do {
		l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
		tmp = l3_maint_ctl.bits.mnt_start;
	} while (tmp != L3_MAINT_END);
	return;
}

static void l3cache_inval_all(void)
{
	unsigned long flags;

	spin_lock_irqsave(&l3cache_lock, flags);
	l3cache_inval_all_nolock();
	cache_sync();
	spin_unlock_irqrestore(&l3cache_lock, flags);
}

static void l3cache_flush_all(void)
{
	unsigned long flags;

	spin_lock_irqsave(&l3cache_lock, flags);
	l3cache_flush_all_nolock();
	cache_sync();
	spin_unlock_irqrestore(&l3cache_lock, flags);
	return;
}

static inline void l3cache_hardware_inval(phys_addr_t start, phys_addr_t end)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	u32 tmp;

	/* step 1:set inval start addr */
	l3cache_writel(start, l3_base_addr + CACHE_L3_MAINT_START);

	/* step 2:set inval end addr */
	l3cache_writel(end, l3_base_addr + CACHE_L3_MAINT_END);

	/* step 3:start inval */
	l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
	l3_maint_ctl.bits.mnt_range = L3_MAINT_FOR_ADDR;
	l3_maint_ctl.bits.mnt_type = L3_MAINT_INVAL;
	l3_maint_ctl.bits.mnt_start = L3_MAINT_START;
	l3cache_writel(l3_maint_ctl.value, l3_base_addr + CACHE_L3_MAINT_CTRL_REG);

	/* step 4:waiting inval Maintain end */
	do {
		l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
		tmp = l3_maint_ctl.bits.mnt_start;
	} while (tmp != L3_MAINT_END);
	return;
}

static void l3cache_hardware_clean(phys_addr_t start, phys_addr_t end)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	u32 tmp;

	/* step 1:set clean start addr */
	l3cache_writel(start, l3_base_addr + CACHE_L3_MAINT_START);

	/* step 2:set clean end addr */
	l3cache_writel(end, l3_base_addr + CACHE_L3_MAINT_END);

	/* step 3:start clean */
	l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
	l3_maint_ctl.bits.mnt_range = L3_MAINT_FOR_ADDR;
	l3_maint_ctl.bits.mnt_type = L3_MAINT_CLEAN;
	l3_maint_ctl.bits.mnt_start = L3_MAINT_START;
	l3cache_writel(l3_maint_ctl.value, l3_base_addr + CACHE_L3_MAINT_CTRL_REG);

	/* step 4:waiting clean Maintain end */
	do {
		l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
		tmp = l3_maint_ctl.bits.mnt_start;
	} while (tmp != L3_MAINT_END);
	return;
}

static void l3cache_hardware_flush(phys_addr_t start, phys_addr_t end)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	u32 tmp;

	/* step 1:set flush start addr */
	l3cache_writel(start, l3_base_addr + CACHE_L3_MAINT_START);

	/* step 2:set flush end addr */
	l3cache_writel(end, l3_base_addr + CACHE_L3_MAINT_END);

	/* step 3:start flush */
	l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
	l3_maint_ctl.bits.mnt_range = L3_MAINT_FOR_ADDR;
	l3_maint_ctl.bits.mnt_type = L3_MAINT_FLUSH;
	l3_maint_ctl.bits.mnt_start = L3_MAINT_START;
	l3cache_writel(l3_maint_ctl.value, l3_base_addr + CACHE_L3_MAINT_CTRL_REG);

	/* step 4:waiting flush Maintain end */
	do {
		l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
		tmp = l3_maint_ctl.bits.mnt_start;
	} while (tmp != L3_MAINT_END);
	return;
}

static void l3cache_inval_range(phys_addr_t start, phys_addr_t end)
{
	unsigned long flags;
	static unsigned int print;

	if ((unlikely(start & (CACHE_LINE_SIZE - 1))) || (unlikely(end & (CACHE_LINE_SIZE - 1)))) {
		if (print != 1) {
			pr_info(DTS_CACHE_WARN "Invalidata L3 cache Range is not cacheline aligned. \
						start physaddr:0x%pa, end phsyaddr:0x%pa.\n", &start, &end);
			print = 1;
			dump_stack();
		}
	}

	spin_lock_irqsave(&l3cache_lock, flags);
	if (unlikely(start & (CACHE_LINE_SIZE - 1))) {
		/* flush the line if it is not cacheline aligned */
		start = start >> CACHE_LINE_BITSHITF;
		l3cache_hardware_flush(start, start + 1);
		start += 1;
	} else {
		start = start >> CACHE_LINE_BITSHITF;
	}

	if (unlikely(end & (CACHE_LINE_SIZE - 1))) {
		/* flush the line if it is not cacheline aligned */
		end = end >> CACHE_LINE_BITSHITF;
		l3cache_hardware_flush(end, end + 1);
	} else {
		end = end >> CACHE_LINE_BITSHITF;
	}

	l3cache_hardware_inval(start, end);
	spin_unlock_irqrestore(&l3cache_lock, flags);

	return;
}

static void l3cache_clean_range(phys_addr_t start, phys_addr_t end)
{
	unsigned long flags;

	spin_lock_irqsave(&l3cache_lock, flags);
	start = start >> CACHE_LINE_BITSHITF;
	end = ((end - 1) >> CACHE_LINE_BITSHITF) + 1;
	l3cache_hardware_clean(start, end);
	spin_unlock_irqrestore(&l3cache_lock, flags);

	return;
}

static void l3cache_flush_range(phys_addr_t start, phys_addr_t end)
{
	unsigned long flags;

	spin_lock_irqsave(&l3cache_lock, flags);
	start = start >> CACHE_LINE_BITSHITF;
	end = ((end - 1) >> CACHE_LINE_BITSHITF) + 1;
	l3cache_hardware_flush(start, end);
	spin_unlock_irqrestore(&l3cache_lock, flags);

	return;
}

static void l3cache_disable(void)
{
	unsigned long flags;

	/* disable L3cache */
	spin_lock_irqsave(&l3cache_lock, flags);
	l3cache_flush_all_nolock();
	l3cache_writel(L3_CTRL_CACHE_DISABLE, l3_base_addr + CACHE_L3_CTRL_REG);
	spin_unlock_irqrestore(&l3cache_lock, flags);
}

#ifdef CONFIG_L3_CACHE_DEBUG
void l3cache_lock_range(phys_addr_t start, phys_addr_t end, u32 way)
{
	unsigned long i;
	unsigned long size;
	char *addr_l3 = NULL;

	size = end - start;
	addr_l3 = (char *)__arm_ioremap(start, size, MT_DEVICE_L3);
	if (!addr_l3)
		return;

	l3cache_flush_range(start, end);

	l3cache_writel(~(way & 0xff), l3_base_addr + CACHE_L3_LOCKDOWN_REG);

	for (i = 0; i < size; i++)
		*(addr_l3 + i) = 0;

	l3cache_writel((way & 0xff), l3_base_addr + CACHE_L3_LOCKDOWN_REG);

	iounmap(addr_l3);
}
EXPORT_SYMBOL(l3cache_lock_range);

static void l3cache_clean_all(void)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	u32 tmp;
	unsigned long flags;

	spin_lock_irqsave(&l3cache_lock, flags);

	/* step 1:clean all */
	l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
	l3_maint_ctl.bits.mnt_range = L3_MAINT_FOR_ALL;
	l3_maint_ctl.bits.mnt_type = L3_MAINT_CLEAN;
	l3_maint_ctl.bits.mnt_start = L3_MAINT_START;
	l3cache_writel(l3_maint_ctl.value, l3_base_addr + CACHE_L3_MAINT_CTRL_REG);

	/* step 2:waiting Maintain end */
	do {
		l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
		tmp = l3_maint_ctl.bits.mnt_start;
	} while (tmp != L3_MAINT_END);

	cache_sync();
	spin_unlock_irqrestore(&l3cache_lock, flags);
	return;
}

#endif

#define MAX_READ_LEN (1024)
#define MAX_WRITE_LEN (16)
#define L3CACHE_FLAG_ENABLE (1)
#define L3CACHE_FLAG_DISABLE (0)

struct proc_dir_entry *cache_ecc;
/* hi1381 has 8 l3 bank,set this to default */
static int l3_bank_num = 8;

static int fill_buffer_with_4bank(char *buffer, int size)
{
	int len = 0;

	len += snprintf(buffer + len, size - len,
				"----------------------------------------l3 cache ecc----------------------------------------\n");
	len += snprintf(buffer + len, size - len, "                 bank0        bank1        bank2        bank3\n");
	len += snprintf(buffer + len, size - len, "DATARAM-1bit     0x%08x   0x%08x   0x%08x   0x%08x\n",
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT1_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK1_EVENT_CNT1_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK2_EVENT_CNT1_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK3_EVENT_CNT1_REG));

	len += snprintf(buffer + len, size - len, "DATARAM-2bit-way 0x%08x   0x%08x   0x%08x   0x%08x\n",
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_DRAM_ECC_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK1_DRAM_ECC_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK2_DRAM_ECC_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK3_DRAM_ECC_REG));

	len += snprintf(buffer + len, size - len, "DATARAM-2bit     0x%08x   0x%08x   0x%08x   0x%08x\n",
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT2_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT2_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT2_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT2_REG));

	len += snprintf(buffer + len, size - len, "TAGRAM-1bit      0x%08x   0x%08x   0x%08x   0x%08x\n",
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT0_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK1_EVENT_CNT0_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK2_EVENT_CNT0_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK3_EVENT_CNT0_REG));

	len += snprintf(buffer + len, size - len, "TAGRAM-2bit-way  0x%08x   0x%08x   0x%08x   0x%08x\n",
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_TRAM_ECC_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK1_TRAM_ECC_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK2_TRAM_ECC_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK3_TRAM_ECC_REG));

	len += snprintf(buffer + len, size - len, "TAGRAM-2bit      0x%08x   0x%08x   0x%08x   0x%08x\n",
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT3_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT3_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT3_REG),
				l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT3_REG));
	return len;
}

static int fill_buffer_with_8bank(char *buffer, int size)
{
	int len = 0;

	len += snprintf(buffer + len, size - len,
		"----------------------------------------l3 cache ecc----------------------------------------\n");
	len += snprintf(buffer + len, size - len,
		"                 bank0        bank1        bank2        bank3        "
		"bank4        bank5        bank6        bank7\n");

	len += snprintf(buffer + len, size - len,
		"DATARAM-1bit     0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x\n",
		l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK1_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK2_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK3_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK4_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK5_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK6_EVENT_CNT1_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK7_EVENT_CNT1_REG));

	len += snprintf(buffer + len, size - len,
		"DATARAM-2bit     0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x\n",
		l3cache_readl(l3_base_addr + CACHE_L3_BANK0_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK1_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK2_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK3_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK4_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK5_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK6_DRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK7_DRAM_ECC_REG));

	len += snprintf(buffer + len, size - len,
		"TAGRAM-1bit      0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x\n",
		l3cache_readl(l3_base_addr + CACHE_L3_BANK0_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK1_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK2_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK3_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK4_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK5_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK6_EVENT_CNT0_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK7_EVENT_CNT0_REG));

	len += snprintf(buffer + len, size - len,
		"TAGRAM-2bit      0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x   0x%08x\n",
		l3cache_readl(l3_base_addr + CACHE_L3_BANK0_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK1_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK2_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK3_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK4_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK5_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK6_TRAM_ECC_REG),
		l3cache_readl(l3_base_addr + CACHE_L3_BANK7_TRAM_ECC_REG));
	return len;
}

static ssize_t proc_l3cache_read(struct file *file, char __user *buf,
				 size_t count, loff_t *ppos)
{
	char *buffer = NULL;
	int len = 0;
	int ret;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	buffer = kzalloc(MAX_READ_LEN, GFP_KERNEL);
	if (!buffer) {
		pr_err(DTS_CACHE_ERR "[proc_l3cache_read]kzalloc error.\n");
		return -ENOMEM;
	}

	/* if there are 4 banks,such as hi1215 */
	if (l3_bank_num == 4)
		len = fill_buffer_with_4bank(buffer, MAX_READ_LEN);
	else
		len = fill_buffer_with_8bank(buffer, MAX_READ_LEN);

	ret = simple_read_from_buffer(buf, count, ppos, buffer, len);
	kfree(buffer);
	return ret;
}

#define HI1215_ECC_EVENT_EN 0x22210001
#define DEFAULT_ECC_EVENT_EN 0xffff0001

static int set_ecc_event(unsigned long value)
{
	unsigned int reg_val;
	int i;
	unsigned int offset = CACHE_L3_BANK0_EVENT_TYPE_REG;

	if (value != L3CACHE_FLAG_ENABLE && value != L3CACHE_FLAG_DISABLE) {
		pr_warn(DTS_CACHE_WARN "error input,do nothing.\n");
		return -EINVAL;
	}

	if (value == L3CACHE_FLAG_ENABLE) {
		pr_info("ECC EVENT ENABLE\n");
		/* if there are 4 banks,such as hi1215 */
		if (l3_bank_num == 4)
			reg_val = HI1215_ECC_EVENT_EN;
		else
			reg_val = DEFAULT_ECC_EVENT_EN;
	} else {
		pr_info("ECC EVENT DISABLE\n");
		reg_val = 0xffffffff;
	}
	for (i = 0; i < l3_bank_num; i++) {
		l3cache_writel(reg_val, l3_base_addr + offset);
		offset += CACHE_L3_EVENT_TYPE_REG_OFFSET;
	}

	return 0;
}

static ssize_t proc_l3cache_write(struct file *file, const char __user *buf,
				  size_t len, loff_t *offs)
{
	char input_string[MAX_WRITE_LEN + 1];
	char *end;
	unsigned long value;
	int ret;

	if (!capable(CAP_SYS_ADMIN))
		return -EPERM;

	if (len > MAX_WRITE_LEN) {
		pr_err(DTS_CACHE_ERR "input too long.\n");
		return -EINVAL;
	}

	ret = copy_from_user(input_string, buf, len);
	if (ret) {
		pr_err(DTS_CACHE_ERR "Copy from user fail!!\n");
		return -EINVAL;
	}
	input_string[len] = 0;

	value = simple_strtoul(input_string, &end, 10);
	if (end - input_string == 0) {
		pr_warn(DTS_CACHE_WARN "strtoul error.\n");
		return -EINVAL;
	}

	ret = set_ecc_event(value);
	if (ret) {
		pr_warn(DTS_CACHE_WARN "set ecc fail\n");
		return -EINVAL;
	}
	return len;
}

static const struct proc_ops l3_proc_fops = {
	.proc_read	= proc_l3cache_read,
	.proc_write  = proc_l3cache_write,
};

int __init l3cache_init(void __iomem *base, __u32 aux_val)
{
	cache_l3_maint_ctrl l3_maint_ctl;
	unsigned long flags;
	unsigned int reg_val;
	u32 tmp;
	u32 auxval = 0;

#ifdef CONFIG_OF
	struct device_node *node;
	/* line 32byte; lines per way: 1024; ways: 8 */
	node = of_find_matching_node(NULL, l3cache_ids);
	if (!node) {
		pr_err(DTS_CACHE_ERR "l3 cache node null\n");
		return -ENODEV;
	}
	l3_base_addr = (u32 __iomem)of_iomap(node, 0);
	if (!l3_base_addr) {
		pr_err(DTS_CACHE_ERR "l3 cache mem alloc failed\n");
		return -ENOMEM;
	}

	if (of_property_read_u32(node, "aux_reg", &auxval)) {
		pr_warn("dts: No aux_reg, used default value!\n");
		auxval |= L3_AUCTRL_EVENT_EN_1381;
		auxval |= L3_AUCTRL_ECC_EN_1381;
	}
	if (of_property_read_u32(node, "l3_bank_num", &l3_bank_num))
		pr_warn("dts: no l3_bank_num, used default value %d!\n", l3_bank_num);
#else
	if (base == NULL) {
		pr_err("Invalid L3cache base address\n");
		return -ENOMEM;
	}
	l3_base_addr = (u32)base;

	auxval = aux_val;
#endif /* CONFIG_OF */

	reg_val = l3cache_readl(l3_base_addr + CACHE_L3_CTRL_REG);
	if ((reg_val & L3_CTRL_REG_MASK) == L3_CTRL_CACHE_DISABLE) {
		/* prevent irq interupt */
		spin_lock_irqsave(&l3cache_lock, flags);

		/* step 1:check maintain is done */
		pr_info("l3 check maintain start, waiting ==");
		do {
			l3_maint_ctl.value = l3cache_readl(l3_base_addr + CACHE_L3_MAINT_CTRL_REG);
			tmp = l3_maint_ctl.bits.mnt_start;
		} while (tmp != L3_MAINT_END);
		pr_info("==end!\n");

		reg_val = l3cache_readl(l3_base_addr + CACHE_L3_AUCTRL_REG);
		reg_val |= auxval;
		l3cache_writel(reg_val, l3_base_addr + CACHE_L3_AUCTRL_REG);

		/* step 2:enable L3 Cache */
		l3cache_writel(L3_CTRL_CACHE_ENABLE,
			       l3_base_addr + CACHE_L3_CTRL_REG);

		spin_unlock_irqrestore(&l3cache_lock, flags);
	} else {
		pr_warn(DTS_CACHE_WARN "[l3cache_init]l3 has enable before driver init.\n");
	}

	outer_cache.inv_range = l3cache_inval_range;
	outer_cache.clean_range = l3cache_clean_range;
	outer_cache.flush_range = l3cache_flush_range;
	outer_cache.flush_all = l3cache_flush_all;
#ifdef CONFIG_L3_CACHE_DEBUG
	/* only used in chip verification, because it has a hardware bug */
	outer_cache.clean_all = l3cache_clean_all;
#endif
	outer_cache.inv_all = l3cache_inval_all;
	outer_cache.disable = l3cache_disable;

#ifdef CONFIG_OUTER_CACHE_SYNC
	outer_cache.sync = l3cache_cache_sync;
#endif

	cache_ecc = proc_create("ecc-l3", S_IRUSR|S_IWUSR|S_IRGRP, NULL, &l3_proc_fops);
	if (!cache_ecc) {
		pr_err(DTS_CACHE_ERR "ecc proc register failed!\n");
		goto PROC_CREATE_FAIL;
	}

	pr_info("L3cache cache controller enabled\n");
	return 0;

PROC_CREATE_FAIL:
	iounmap((void *)l3_base_addr);
	return -ENOMEM;
}

#ifdef CONFIG_OF
static int __init early_l3cache_init(void)
{
	return l3cache_init(0, 0);
}
arch_initcall(early_l3cache_init);
#endif

void l3cache_exit(void)
{
	unsigned int val;

	if (!l3_base_addr)
		return;

	remove_proc_entry("ecc-l3", NULL);

	val = l3cache_readl(l3_base_addr + CACHE_L3_CTRL_REG);
	/*
	 * Due to hi1381's restriction, flush ops after l3cache has been disabled may casues dirty
	 * data to DDR.
	 * Some board uses wdt to triggle reboot process, if hooks registered to rtos_snapshot.ko
	 * call flush ops, diry data may be flushed to ddr probably, then this leads to uncertain
	 * results like panic etc.
	 * According to hisi FAE's advice, disable l3 cache is not necessary, just flush data all.
	 */
	if ((val & L3_CTRL_REG_MASK) == L3_CTRL_CACHE_ENABLE)
		l3cache_flush_all();

	/*
	 * Hi1381, iounmap in reboot will occur a panic,
	 * we delete "iounmap((void*)l3_base_addr);" at here.
	 */

	return;
}
